Popular Searches
Popular Course Categories
Popular Courses

Introduction to Provider

Introduction to Provider

Flutter State Management

Introduction to Provider in Flutter

Provider is a popular state-management and dependency-injection package used in Flutter applications. It makes it easier to expose application data or objects to widgets and allows widgets to listen to changes and rebuild when required.

Flutter's official documentation demonstrates Provider together with ChangeNotifier as a simple approach to application state management. The Provider package is built on top of Flutter's inherited-widget mechanism and reduces the amount of boilerplate required to share state between widgets.


1. What is Provider?

Provider is a Flutter package that helps widgets access shared data or objects from the widget tree. Instead of passing data manually through multiple constructors, a provider can expose the data at a higher level and descendant widgets can retrieve it when needed.

The official Provider package describes itself as a wrapper around InheritedWidget that makes exposing and consuming values easier and more reusable.

Simple Example

Provider(
  create: (_) => 'Hello Flutter',
  child: MyHomePage(),
)

A descendant widget can access the value using:

final message = context.watch();

2. Why Do We Need Provider?

As a Flutter application becomes larger, managing state with only setState() can become difficult. State may need to be shared between multiple widgets, screens, or application features.

Provider helps solve problems such as:

  • Sharing state between multiple widgets.
  • Avoiding unnecessary constructor-based data passing.
  • Separating business logic from UI code.
  • Listening to state changes.
  • Rebuilding only the widgets that depend on changed data.
  • Managing objects and dependencies through the widget tree.
  • Making application architecture easier to organize.

3. Provider and Flutter State Management

Flutter applications are declarative. The UI represents the current state of the application. When state changes, the widgets that depend on that state can rebuild and display the updated information.

Provider gives developers a convenient way to expose state and allow widgets to consume it.

State changes
     ↓
ChangeNotifier updates data
     ↓
notifyListeners()
     ↓
Listening widgets are notified
     ↓
Widgets rebuild
     ↓
Updated UI

4. Installing Provider

Add the Provider package to your Flutter project using the following command:

flutter pub add provider

You can also add it manually to pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.5+1

After adding the dependency, run:

flutter pub get

Package versions can change over time, so check the current Provider package information when starting a new project.


5. Importing Provider

After installing the package, import it into your Dart file:

import 'package:provider/provider.dart';

6. Important Provider Concepts

When learning Provider, the following concepts are especially important:

ConceptPurpose
ProviderExposes a value or object to descendant widgets.
ChangeNotifierProvides a mechanism for notifying listeners when data changes.
ChangeNotifierProviderProvides a ChangeNotifier to descendant widgets.
ConsumerListens to a provider and rebuilds its builder when the value changes.
context.watch()Reads a provider and listens for changes.
context.read()Reads a provider without listening for changes.
context.select()Listens only to a selected portion of provider state.
Provider.of()Provides another way to access a provider.
SelectorHelps optimize rebuilds by selecting a specific value.
MultiProviderAllows multiple providers to be organized together.

7. Understanding ChangeNotifier

ChangeNotifier is a Flutter class that allows an object to notify its listeners when its state changes.

A common pattern is to create a model or ViewModel that extends ChangeNotifier.

class CounterModel extends ChangeNotifier {
  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}

Here, count represents the state and notifyListeners() informs widgets listening to this object that the state has changed.


8. Understanding notifyListeners()

notifyListeners() is used after changing data when the UI needs to respond to that change.

void increment() {
  count++;
  notifyListeners();
}

Without notifying listeners, widgets that depend on the ChangeNotifier may not rebuild in response to that change.

Example

class UserModel extends ChangeNotifier {
  String name = 'Guest';

  void updateName(String newName) {
    name = newName;
    notifyListeners();
  }
}

9. What is ChangeNotifierProvider?

ChangeNotifierProvider is commonly used when the application state is represented by a ChangeNotifier.

ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: MyApp(),
)

Descendant widgets can then access the CounterModel.

When a ChangeNotifier created by ChangeNotifierProvider is no longer needed, Provider can automatically dispose of it.


10. Basic Provider Architecture

A simple Provider-based application can be organized into three main parts:

  1. Model: Stores state and business logic.
  2. Provider: Makes the model available to descendant widgets.
  3. UI: Reads the state and displays the result.
Model
  ↓
ChangeNotifier
  ↓
ChangeNotifierProvider
  ↓
Consumer / context.watch()
  ↓
Flutter UI

11. Creating a Counter Model

Let's create a simple counter model:

import 'package:flutter/foundation.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    _count--;
    notifyListeners();
  }

  void reset() {
    _count = 0;
    notifyListeners();
  }
}

Why Use Private Variables?

The _count variable is private to the class. The UI accesses it through the public getter:

int get count => _count;

This allows the model to control how its state is modified.


12. Providing the Counter Model

The model can be provided above the widgets that need it:

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

Any descendant of this provider can access the CounterModel.


13. Complete Counter Example with Provider

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    final counter = context.watch();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Provider Counter'),
      ),
      body: Center(
        child: Text(
          '${counter.count}',
          style: const TextStyle(fontSize: 40),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read().increment();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

14. How the Counter Example Works

  1. CounterModel extends ChangeNotifier.
  2. The counter value is stored inside the model.
  3. ChangeNotifierProvider creates and exposes the model.
  4. context.watch() listens to the model.
  5. The button uses context.read() to call the increment method.
  6. increment() changes the state.
  7. notifyListeners() informs listeners.
  8. The listening UI rebuilds with the new value.

15. What is Consumer?

Consumer is a Provider widget used to obtain a provided value and rebuild a specific portion of the widget tree when that value changes.

Consumer(
  builder: (context, counter, child) {
    return Text('${counter.count}');
  },
)

The generic type tells Provider which object should be consumed.


16. Consumer Example

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Consumer(
          builder: (context, counter, child) {
            return Text(
              '${counter.count}',
              style: const TextStyle(fontSize: 40),
            );
          },
        ),
      ),
    );
  }
}

17. Consumer with Button Actions

Consumer(
  builder: (context, counter, child) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Count: ${counter.count}'),
        ElevatedButton(
          onPressed: counter.increment,
          child: const Text('Increment'),
        ),
      ],
    );
  },
)

18. context.watch()

context.watch() obtains a provider value and listens for changes.

final counter = context.watch();
Text('${counter.count}')

When the provider notifies listeners, the widget using watch can rebuild.


19. context.read()

context.read() obtains a provider value without establishing a listening relationship.

ElevatedButton(
  onPressed: () {
    context.read().increment();
  },
  child: const Text('Increment'),
)

This is useful when you need to call a method but do not need the widget to rebuild because of that provider's changes.


20. context.select()

context.select() can be used when a widget needs to listen to only a specific part of a provider's state.

final count = context.select(
  (counter) => counter.count,
);

This can help reduce unnecessary rebuilds when the provider contains multiple pieces of state.


21. Consumer vs context.watch()

FeatureConsumercontext.watch()
Access providerYesYes
Listens for changesYesYes
Creates a rebuild boundaryYesWidget using watch rebuilds
Useful for localized rebuildsYesCan be less localized

22. Consumer vs context.read()

Consumercontext.read()
Listens to changesDoes not listen
Rebuilds when provider changesDoes not rebuild because of provider changes
Useful for displaying provider stateUseful for calling provider methods

23. Provider.of()

Another way to access a provider is Provider.of().

final counter = Provider.of(context);

By default, this form listens for changes.

You can disable listening:

final counter = Provider.of(
  context,
  listen: false,
);

In modern Provider code, context.watch() and context.read() are often easier to read.


24. Provider Placement

A provider must be placed above the widgets that need to access it.

ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: MaterialApp(
    home: CounterPage(),
  ),
)

Here, CounterPage can access CounterModel because it is below the provider in the widget tree.


25. Understanding Provider Scope

Provider follows the Flutter widget-tree structure. A widget can access a provider that exists in its ancestor tree.

ChangeNotifierProvider
        |
        +-- HomePage
        |     |
        |     +-- CounterWidget
        |
        +-- ProfilePage

Both pages can access the provider if they are descendants of the provider.


26. MultiProvider

When an application contains multiple providers, MultiProvider can make the provider structure easier to read.

MultiProvider(
  providers: [
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
    ),
    ChangeNotifierProvider(
      create: (_) => UserModel(),
    ),
  ],
  child: const MyApp(),
)

27. Multiple Providers Example

class UserModel extends ChangeNotifier {
  String name = 'Guest';

  void updateName(String value) {
    name = value;
    notifyListeners();
  }
}

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => CounterModel(),
        ),
        ChangeNotifierProvider(
          create: (_) => UserModel(),
        ),
      ],
      child: const MyApp(),
    ),
  );
}

28. Provider for Shopping Cart State

Provider is useful for application-level features such as shopping carts.

class CartModel extends ChangeNotifier {
  final List _items = [];

  List get items => List.unmodifiable(_items);

  void addItem(String item) {
    _items.add(item);
    notifyListeners();
  }

  void removeItem(String item) {
    _items.remove(item);
    notifyListeners();
  }

  void clearCart() {
    _items.clear();
    notifyListeners();
  }

  int get itemCount => _items.length;
}

29. Reading Cart State

final cart = context.watch();
Text(
  'Items: ${cart.itemCount}',
)

30. Updating Cart State

ElevatedButton(
  onPressed: () {
    context.read().addItem('Flutter Course');
  },
  child: const Text('Add Item'),
)

31. Provider for Login State

Provider can be used to represent authentication-related application state.

class AuthModel extends ChangeNotifier {
  bool _isLoggedIn = false;

  bool get isLoggedIn => _isLoggedIn;

  void login() {
    _isLoggedIn = true;
    notifyListeners();
  }

  void logout() {
    _isLoggedIn = false;
    notifyListeners();
  }
}

The UI can display different screens based on isLoggedIn.


32. Provider for Theme State

class ThemeModel extends ChangeNotifier {
  bool _isDark = false;

  bool get isDark => _isDark;

  void toggleTheme() {
    _isDark = !_isDark;
    notifyListeners();
  }
}

A widget can listen to this state and update the application's theme.


33. Provider for Loading, Success, and Error States

Provider can also represent asynchronous application states.

enum ViewState {
  idle,
  loading,
  success,
  error,
}

class DataModel extends ChangeNotifier {
  ViewState state = ViewState.idle;
  String? errorMessage;

  Future loadData() async {
    state = ViewState.loading;
    notifyListeners();

    try {
      await Future.delayed(const Duration(seconds: 2));
      state = ViewState.success;
    } catch (e) {
      state = ViewState.error;
      errorMessage = e.toString();
    }

    notifyListeners();
  }
}

34. Displaying Loading and Error States

Consumer(
  builder: (context, data, child) {
    if (data.state == ViewState.loading) {
      return const CircularProgressIndicator();
    }

    if (data.state == ViewState.error) {
      return Text(
        data.errorMessage ?? 'Something went wrong',
      );
    }

    if (data.state == ViewState.success) {
      return const Text('Data loaded successfully');
    }

    return ElevatedButton(
      onPressed: data.loadData,
      child: const Text('Load Data'),
    );
  },
)

35. Provider with API Services

Provider can be used not only for UI state but also to expose services and dependencies.

class ApiService {
  Future fetchData() async {
    return 'Server Data';
  }
}

Provide the service:

Provider(
  create: (_) => ApiService(),
  child: const MyApp(),
)

Read it from a widget:

final api = context.read();

36. Providing Dependencies with Provider

Provider can expose objects such as:

  • API services
  • Repositories
  • Database services
  • Authentication services
  • Application configuration
  • ChangeNotifier-based ViewModels

This can reduce the need to create the same object manually inside many widgets.


37. Provider and Repository Architecture

A larger Flutter application can separate responsibilities into layers:

UI
 ↓
ViewModel / ChangeNotifier
 ↓
Repository
 ↓
Service
 ↓
API / Database

For example:

class UserRepository {
  Future fetchUser() async {
    return 'John';
  }
}

class UserModel extends ChangeNotifier {
  final UserRepository repository;

  UserModel(this.repository);

  String? name;

  Future loadUser() async {
    name = await repository.fetchUser();
    notifyListeners();
  }
}

38. Selector

Selector can be used when a widget needs only a particular part of a provider's state.

Selector(
  selector: (context, user) => user.name,
  builder: (context, name, child) {
    return Text(name);
  },
)

This can help prevent unnecessary rebuilds when unrelated properties change.


39. Consumer with child

Consumer supports a child parameter. The child can be created outside the builder and reused without rebuilding when the provider changes.

Consumer(
  child: const Icon(Icons.star),
  builder: (context, counter, child) {
    return Column(
      children: [
        Text('${counter.count}'),
        child!,
      ],
    );
  },
)

40. Provider.of() with listen: false

When using Provider.of(), setting listen: false allows access without listening for changes.

final counter = Provider.of(
  context,
  listen: false,
);
counter.increment();

This is conceptually similar to using context.read().


41. Provider.of() with Listening

Without listen: false, Provider can establish a listening relationship:

final counter = Provider.of(context);
return Text('${counter.count}');

The widget can rebuild when the provider notifies its listeners.


42. Provider and setState()

setState() and Provider are not necessarily competitors. They solve different state-management needs and can be used together.

setState()Provider
Simple local stateShared or application state
Usually limited to a StatefulWidgetCan expose state to many descendant widgets
Easy for small UI interactionsUseful for larger application state
Minimal setupRequires provider setup

43. When Should You Use Provider?

Provider can be useful when state needs to be shared by multiple widgets or when business logic should be separated from the UI.

Common examples include:

  • Shopping cart
  • User profile
  • Authentication state
  • Application settings
  • Theme preferences
  • API data
  • Loading and error states
  • Form-related application state
  • Repositories and services

44. When Should You Use setState() Instead?

For small, local, widget-specific state, setState() may be sufficient.

Examples:

  • Selected tab
  • Whether a password is visible
  • Animation-related local state
  • Temporary UI toggles
  • Current page of a small local widget

45. Provider Best Practices

  • Keep business logic outside the UI where practical.
  • Use meaningful model names.
  • Call notifyListeners() after relevant state changes.
  • Do not call notifyListeners() unnecessarily.
  • Keep providers at an appropriate scope.
  • Use context.read() for actions when listening is unnecessary.
  • Use context.watch() when UI needs to react to changes.
  • Use context.select() or Selector when only part of the state is required.
  • Use MultiProvider when multiple providers make the widget tree difficult to read.
  • Keep large models organized and focused.

46. create vs .value

Provider distinguishes between creating a new object and exposing an existing object.

Creating a New Object

ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: MyApp(),
)

Providing an Existing Object

ChangeNotifierProvider.value(
  value: existingCounter,
  child: MyApp(),
)

The Provider documentation recommends using create for new objects and .value when you already have an existing instance that you want to expose.


47. Provider and Automatic Disposal

When a ChangeNotifier is created by ChangeNotifierProvider, Provider manages its lifecycle and can dispose of the notifier when it is no longer needed.

This is one reason why the appropriate Provider constructor should be selected based on whether the object is newly created or already exists.


48. Provider and Lazy Creation

Provider's create and update callbacks are lazy by default. This means the object is generally created when it is first requested.

Lazy behavior can be disabled when required:

Provider(
  create: (_) => ApiService(),
  lazy: false,
  child: MyApp(),
)

49. Common Provider Error: ProviderNotFoundException

A common error occurs when a widget tries to access a provider that is not available above it in the widget tree.

final counter = context.watch();

If no matching provider exists in the ancestor tree, Provider can throw a ProviderNotFoundException.

Solution

Make sure the provider is placed above the widget that consumes it.


50. Common Provider Error: Wrong BuildContext

Another common issue occurs when the BuildContext used to access the provider belongs to a widget that is above the provider instead of below it.

For example, placing a provider inside a widget and attempting to access it using the same widget's context may cause a lookup problem.

Better Structure

ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: CounterPage(),
)

Now the CounterPage context is below the provider.


51. Common Provider Mistakes

  • Forgetting to add the Provider package.
  • Forgetting to import package:provider/provider.dart.
  • Placing the provider below the widget that needs it.
  • Using context.watch() when listening is unnecessary.
  • Using context.read() when the UI actually needs to react to changes.
  • Forgetting notifyListeners().
  • Calling notifyListeners() without changing meaningful state.
  • Using ChangeNotifierProvider.value incorrectly to create new objects.
  • Making a single ChangeNotifier responsible for too many unrelated features.
  • Ignoring rebuild performance in large widget trees.

52. Provider Project Structure

A scalable project may organize Provider-related code like this:

lib/
├── main.dart
├── models/
│   ├── user.dart
│   └── product.dart
├── providers/
│   ├── auth_provider.dart
│   ├── cart_provider.dart
│   └── theme_provider.dart
├── services/
│   ├── api_service.dart
│   └── auth_service.dart
├── repositories/
│   └── user_repository.dart
└── screens/
    ├── home_screen.dart
    ├── login_screen.dart
    └── profile_screen.dart

53. Example Provider Class

import 'package:flutter/foundation.dart';

class UserProvider extends ChangeNotifier {
  String _name = 'Guest';
  bool _loading = false;

  String get name => _name;
  bool get loading => _loading;

  Future loadUser() async {
    _loading = true;
    notifyListeners();

    await Future.delayed(const Duration(seconds: 2));

    _name = 'Flutter Student';
    _loading = false;
    notifyListeners();
  }
}

54. Registering Multiple Providers

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => UserProvider(),
        ),
        ChangeNotifierProvider(
          create: (_) => CartModel(),
        ),
        ChangeNotifierProvider(
          create: (_) => ThemeModel(),
        ),
      ],
      child: const MyApp(),
    ),
  );
}

55. Consuming Multiple Providers

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final user = context.watch();
    final cart = context.watch();

    return Column(
      children: [
        Text('User: ${user.name}'),
        Text('Cart items: ${cart.itemCount}'),
      ],
    );
  }
}

56. Provider and Firebase

Provider can be combined with Firebase services to separate Firebase operations from UI code.

For example:

Firebase Authentication
        ↓
Auth Service
        ↓
Auth Provider
        ↓
Flutter UI

The Provider can hold authentication-related state while the service communicates with Firebase.


57. Provider with Authentication State

class AuthProvider extends ChangeNotifier {
  bool _isLoggedIn = false;

  bool get isLoggedIn => _isLoggedIn;

  Future login() async {
    _isLoggedIn = true;
    notifyListeners();
  }

  Future logout() async {
    _isLoggedIn = false;
    notifyListeners();
  }
}

The application can use this provider to decide which screen should be displayed.


58. Provider with Form State

Provider can also manage more complex form state.

class RegistrationProvider extends ChangeNotifier {
  String name = '';
  String email = '';
  bool isSubmitting = false;

  void setName(String value) {
    name = value;
    notifyListeners();
  }

  void setEmail(String value) {
    email = value;
    notifyListeners();
  }

  Future submit() async {
    isSubmitting = true;
    notifyListeners();

    await Future.delayed(const Duration(seconds: 2));

    isSubmitting = false;
    notifyListeners();
  }
}

59. Provider and Separation of Concerns

One of the main advantages of Provider is that it can help separate UI code from application logic.

Instead of putting all business logic inside a widget:

Widget
 ├── UI
 ├── API logic
 ├── Validation
 ├── State management
 └── Business rules

You can separate responsibilities:

Widget
 ↓
Provider / ViewModel
 ↓
Repository
 ↓
Service
 ↓
Data Source

60. Provider and Rebuild Optimization

When a provider changes, widgets listening to it may rebuild. For large applications, it is useful to limit rebuilds to the parts of the UI that actually depend on changed data.

Useful tools include:

  • Consumer
  • Selector
  • context.select()
  • The child parameter of Consumer

61. Example of context.select()

class UserModel extends ChangeNotifier {
  String name = 'Manish';
  int age = 25;

  void updateAge(int value) {
    age = value;
    notifyListeners();
  }
}

If a widget only needs the name:

final name = context.select(
  (user) => user.name,
);
return Text(name);

Changes to unrelated properties do not need to cause this widget to rebuild when the selected value remains unchanged.


62. Provider vs Other State Management Approaches

ApproachTypical Use
setState()Simple local widget state.
ProviderShared application state and dependency access.
RiverpodProvider-style state management with a different architecture and API.
Bloc/CubitStructured event/state or business-logic-driven applications.
ValueNotifierSmall observable values.

The appropriate approach depends on the application's requirements, team preferences, architecture, and complexity.


63. Provider Advantages

  • Simple API.
  • Works naturally with Flutter's widget tree.
  • Reduces manual InheritedWidget boilerplate.
  • Supports dependency access.
  • Supports ChangeNotifier-based state management.
  • Provides several ways to consume state.
  • Supports rebuild optimization.
  • Can be used with services and repositories.
  • Works for both small and larger applications when structured appropriately.

64. Provider Limitations

  • Developers still need to understand widget-tree scope.
  • Incorrect provider placement can cause runtime errors.
  • Large ChangeNotifier classes can become difficult to maintain.
  • Excessive use of notifyListeners() can cause unnecessary rebuilds.
  • Complex applications may require additional architectural patterns.
  • Developers must understand the difference between reading and listening.

65. Provider Best Practice Example

class CartProvider extends ChangeNotifier {
  final List _items = [];

  List get items => List.unmodifiable(_items);

  int get totalItems => _items.length;

  void add(String item) {
    _items.add(item);
    notifyListeners();
  }

  void remove(String item) {
    _items.remove(item);
    notifyListeners();
  }

  void clear() {
    if (_items.isEmpty) {
      return;
    }

    _items.clear();
    notifyListeners();
  }
}

This example keeps the internal list private and exposes a read-only view to consumers.


66. Complete Practical Provider Example

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CounterProvider extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    if (_count > 0) {
      _count--;
      notifyListeners();
    }
  }

  void reset() {
    _count = 0;
    notifyListeners();
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterProvider(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Provider Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
        ),
      ),
      home: const CounterScreen(),
    );
  }
}

class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Provider Demo'),
      ),
      body: Center(
        child: Consumer(
          builder: (context, counter, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text(
                  'Current Count',
                  style: TextStyle(fontSize: 20),
                ),
                const SizedBox(height: 10),
                Text(
                  '${counter.count}',
                  style: const TextStyle(
                    fontSize: 50,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 20),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ElevatedButton(
                      onPressed: counter.decrement,
                      child: const Text('Decrease'),
                    ),
                    const SizedBox(width: 10),
                    ElevatedButton(
                      onPressed: counter.increment,
                      child: const Text('Increase'),
                    ),
                    const SizedBox(width: 10),
                    ElevatedButton(
                      onPressed: counter.reset,
                      child: const Text('Reset'),
                    ),
                  ],
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

67. Step-by-Step Flow of the Practical Example

  1. The application starts.
  2. ChangeNotifierProvider creates CounterProvider.
  3. CounterScreen is placed below the provider.
  4. Consumer obtains the CounterProvider.
  5. The current counter value is displayed.
  6. The user presses the Increase button.
  7. The increment() method changes the counter.
  8. notifyListeners() notifies listening widgets.
  9. The Consumer rebuilds.
  10. The updated counter value appears on the screen.

68. Provider Interview Questions

Q1. What is Provider in Flutter?

Provider is a package that simplifies exposing and consuming objects and application state through the Flutter widget tree.

Q2. What is ChangeNotifier?

ChangeNotifier is a Flutter class that allows an object to notify registered listeners when its state changes.

Q3. What does notifyListeners() do?

It notifies listeners that the ChangeNotifier's state has changed so listening widgets can respond.

Q4. What is ChangeNotifierProvider?

It provides a ChangeNotifier to descendant widgets and manages its lifecycle when the notifier is created through the provider.

Q5. What is Consumer?

Consumer obtains a provider value and rebuilds its builder when the provided value changes.

Q6. Difference between context.watch() and context.read()?

context.watch() listens for changes, while context.read() obtains the value without listening.

Q7. What is context.select()?

It allows a widget to listen to a selected portion of provider state.

Q8. What is MultiProvider?

MultiProvider allows multiple providers to be grouped into a more readable widget structure.

Q9. What is ProviderNotFoundException?

It commonly occurs when a widget tries to obtain a provider that is not available in its ancestor widget tree.


69. Quick Revision

TermRemember
ProviderExpose values and dependencies.
ChangeNotifierNotify listeners about state changes.
notifyListeners()Notify listening widgets.
ChangeNotifierProviderProvide a ChangeNotifier.
ConsumerListen and rebuild a specific widget area.
context.watch()Read and listen.
context.read()Read without listening.
context.select()Listen to selected state.
SelectorOptimize rebuilds by selecting state.
MultiProviderOrganize multiple providers.

70. Learning Outcome

After completing this topic, you should be able to:

  • Explain what Provider is.
  • Understand the role of Provider in Flutter state management.
  • Install and configure the Provider package.
  • Create a ChangeNotifier.
  • Use ChangeNotifierProvider.
  • Use Consumer.
  • Use context.watch(), context.read(), and context.select().
  • Use Provider.of().
  • Use MultiProvider.
  • Manage shared application state.
  • Separate business logic from UI code.
  • Optimize unnecessary widget rebuilds.
  • Use Provider with services and repositories.
  • Understand common Provider errors and their solutions.

71. Practical Exercises

  1. Create a counter application using Provider.
  2. Create a shopping cart using ChangeNotifier.
  3. Create a theme switcher using Provider.
  4. Create a login state provider.
  5. Create a user profile provider.
  6. Create a loading, success, and error state provider.
  7. Create a Provider-based API service.
  8. Create an application using multiple providers with MultiProvider.
  9. Use Selector to optimize a widget rebuild.
  10. Build a complete mini application using Provider and Firebase.

72. Important Provider Concepts at a Glance

Provider
   ↓
Expose a value/object

ChangeNotifier
   ↓
Store and change state

ChangeNotifierProvider
   ↓
Expose ChangeNotifier

Consumer
   ↓
Listen and rebuild UI

context.watch()
   ↓
Read + listen

context.read()
   ↓
Read without listening

context.select()
   ↓
Read selected state

Selector
   ↓
Optimize rebuilds

MultiProvider
   ↓
Manage multiple providers

73. Useful Flutter Resources

For official Flutter state-management concepts, refer to the Flutter documentation:

Flutter State Management Documentation

For the Provider package and current package information:

Provider Package on pub.dev


74. JustAcademy Flutter Resources

Learn more about Flutter development and structured Flutter training through JustAcademy:

JustAcademy Flutter Training Course

Register for Flutter Course Demo


75. Summary

Provider is a convenient approach for sharing data, dependencies, and application state through the Flutter widget tree. It works especially well with ChangeNotifier, ChangeNotifierProvider, and Consumer. Developers can use context.watch() when a widget needs to react to changes, context.read() when it only needs to access or modify a value, and context.select() when only a specific portion of state should be observed.

For small local UI state, setState() can remain a simple solution. As an application grows and state needs to be shared across widgets or separated from UI code, Provider can provide a structured way to manage that state and its dependencies.

whatsapp